package com.lam.common.components.easyexcel.imp.listener;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

import javax.validation.groups.Default;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.lam.common.components.dict.DictUtil;
import com.lam.common.components.easyexcel.imp.model.ImportEntry;
import com.lam.common.components.easyexcel.imp.model.ImportResultHandler;
import com.lam.common.components.easyexcel.imp.model.ImportWrapper;
import com.lam.common.components.file.FileDownloadUtil;
import com.lam.common.components.file.model.FileBusiTypeEnum;
import com.lam.common.utils.CollectionUtil;
import com.lam.common.utils.DateUtil;
import com.lam.common.utils.JsonUtil;
import com.lam.common.utils.StringUtils;
import com.lam.common.validate.validator.DictValidBean;

/**
 * @author lam
 *
 * @param <Ie> 用于接收数据对象的Java类
 * @param <R> 用于保存数据对象的Java类
 */
public abstract class BaseListener<Ie, R> extends AnalysisEventListener<Ie> {
	
	protected final Logger logger = LoggerFactory.getLogger(this.getClass());
	
	/** 每隔500条存储数据库，然后清理list，方便内存回收 */
	protected int batchCount = 500;
	/** 本次导入的批次号 */
	protected String batchNo;
	
	/** 导入操作开始时间(毫秒时间戳) */
	protected long startTime;
	/** 导入操作结束时间(毫秒时间戳) */
	protected long endTime;
	/** 导入操作耗时(毫秒) */
	protected long costTime;
	
    /** 处理轮次 */
	protected int turnCount = 1;
    /** 总计数量 */
	protected int totalCount = 0;
    /** 成功数量 */
	protected int successCount = 0;
    /** 失败数量 */
	protected int failCount = 0;
	
	/** 字典数据校验器 */
	protected DictValidBean dictValidator;
	
	/** 用于接收数据对象的Java类 */
	protected Class<Ie> importDataClazz;
	/** 用于保存数据对象的Java类 */
	protected Class<R> targetClazz;
	/** 当前处理轮次的数据 */
	protected List<ImportEntry<Ie, R>> successList = new ArrayList<>();//表格数据list
    /** 当前处理轮次失败记录 */
	protected List<ImportEntry<Ie, R>> failList = new ArrayList<>();
    
    /** 导入结果写回到Excel文件  */
    protected boolean resultSave2Excel = true;
    protected ImportResultHandler<Ie, R> importResultHandler = null;
    
    /** 是否支持更新 */
    protected boolean updateSupport = false;

	public BaseListener(Class<Ie> importDataClazz, Class<R> targetClazz) {
		super();
		this.importDataClazz = importDataClazz;
		this.targetClazz = targetClazz;
		this.startTime = System.currentTimeMillis();
		this.init();
	}
	
	protected void init() {
		this.batchNo = genBatchNo();
		this.dictValidator = DictValidBean.newInstance(importDataClazz, Default.class);
	}
	
	public Map<String, Object> getOperResult(){
		Map<String, Object> map = new HashMap<>();
		map.put("batchNo", batchNo);
		map.put("totalCount", totalCount);
		map.put("successCount", successCount);
		map.put("failCount", failCount);
		if(resultSave2Excel) {
			if(importResultHandler != null) {
				map.put("resultDetail", FileDownloadUtil.createDownloadInfo(FileBusiTypeEnum.DOWNLOAD_TMP, importResultHandler.getFilePath()));
			}else {
				map.put("successAll", true);
			}
		}
		return map;
	}
	
	@Override
	public void invoke(Ie data, AnalysisContext context) {
		
		ImportEntry<Ie, R> wrapper = new ImportEntry<>(data);
		totalCount++;
		String result = checkData(wrapper);
    	if(StringUtils.isNotBlank(result)){
    		wrapper.setImportResult("校验未通过, " + result);
    		failCount++;
    		failList.add(wrapper);
    		// 达到batchCount，需要去保存一次，防止太多数据在内存中，容易造成OOM
    		if (failList.size() >= batchCount) {
    			saveFailData(failList, false);
            	failList.clear();// 存储完成清理 list
                turnCount++;
            }
    		return;
        }

    	wrapper.setImportResult("导入成功！");
    	successCount++;
    	successList.add(wrapper);
		
    	// 达到batchCount，需要去保存一次，防止太多数据在内存中，容易造成OOM
        if (successList.size() >= batchCount) {
        	saveData();
        	successList.clear();// 存储完成清理 list
            turnCount++;
        }
	}
	
	/**
	 * 校验数据
	 * @param data
	 * @return
	 */
	protected String checkData(ImportEntry<Ie, R> wrapper) {
		String msg = dictValidator.check(wrapper.getOrigObject());
		if(StringUtils.isBlank(msg)) {
			wrapper.setData(convertData(wrapper.getOrigObject()));
		}
		return msg;
	}
	
	@SuppressWarnings("unchecked")
	protected R convertData(Ie data) {
		//转换对象
		if(importDataClazz.equals(targetClazz)) {
			return (R)data;
		}
		Object object = DictUtil.convertSimpleObject(data);
		return JsonUtil.toJavaObject(object, targetClazz);
	}
	
	/** 保存解析成功的数据 
	 * @param dataList 
	 * @param updateSupport
	 */
	protected abstract void saveSuccessData(ImportWrapper<Ie, R> wrapper);
	
	protected void saveData() {
		ImportWrapper<Ie, R> wrapper = new ImportWrapper<>(successList, updateSupport);
		saveSuccessData(wrapper);
		if(CollectionUtil.isNotEmpty(wrapper.getFailList())) {
			failList.addAll(wrapper.getFailList());
			successCount -= wrapper.getFailList().size();
			failCount += wrapper.getFailList().size();
		}
		successList.clear();// 存储完成清理 list
	}
	
	/** 保存失败的数据 
	 * @param list 
	 * 
	 */
	protected void saveFailData(List<ImportEntry<Ie, R>> list, boolean close) {
		if(resultSave2Excel) {
			if(importResultHandler == null) {
				importResultHandler = new ImportResultHandler<>(String.format("导入结果-%s.xlsx", DateUtil.formatDate(new Date(), DateUtil.PATTERN_YYYYMMDDHHMMSS_SSS)), importDataClazz);
			}
			
			importResultHandler.write(list);
			if(close) {
				importResultHandler.close();
			}
		}
	}

	/**
     * 所有数据解析完成后会调用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据，确保最后遗留的数据也存储到数据库
    	if(CollectionUtil.isNotEmpty(successList)){
    		saveData();
    		successList.clear();// 存储完成清理 list
    	}
    	if(CollectionUtil.isNotEmpty(failList)){
    		saveFailData(failList, true);
    		failList.clear();
    	}
    	endTime = System.currentTimeMillis();
    	costTime = endTime - startTime;
        logger.info("导入结束, 所有数据解析完成！ batchNo:{}, 共 {} 条, 成功 {} 条, 失败 {} 条, 耗时:{}！", batchNo, totalCount, successCount, failCount, DateUtil.convertTimeLong(costTime));
    }
    
    /**
     * 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。
     *
     * @param exception
     * @param context
     * @throws Exception
     */
    @Override
    public void onException(Exception exception, AnalysisContext context) {
        logger.error("Excel行解析失败，继续解析下一行:{}", exception.getMessage());
        // 如果是某一个单元格的转换异常 能获取到具体行号
        // 如果要获取头的信息 配合invokeHeadMap使用
        if (exception instanceof ExcelDataConvertException) {
            ExcelDataConvertException e = (ExcelDataConvertException)exception;
            logger.error("Excel行解析异常，第{}行，第{}列解析异常，数据为:{}", e.getRowIndex(), e.getColumnIndex(), e.getCellData());
        }
    }

	public String genBatchNo(){
		return String.format("%s%s", DateUtil.formatDate(new Date(), DateUtil.PATTERN_YYYYMMDDHHMMSS_SSS), new Random().nextInt(100));
	}

	public String getBatchNo() {
		return batchNo;
	}

	public void setBatchNo(String batchNo) {
		this.batchNo = batchNo;
	}

	public int getBatchCount() {
		return batchCount;
	}

	public int getTurnCount() {
		return turnCount;
	}

	public int getTotalCount() {
		return totalCount;
	}

	public int getSuccessCount() {
		return successCount;
	}

	public int getFailCount() {
		return failCount;
	}

	public Logger getLogger() {
		return logger;
	}

	public long getStartTime() {
		return startTime;
	}

	public long getEndTime() {
		return endTime;
	}

	public long getCostTime() {
		return costTime;
	}

	public Class<Ie> getImportDataClazz() {
		return importDataClazz;
	}

	public void setImportDataClazz(Class<Ie> importDataClazz) {
		this.importDataClazz = importDataClazz;
	}

	public boolean isResultSave2Excel() {
		return resultSave2Excel;
	}

	public void setResultSave2Excel(boolean resultSave2Excel) {
		this.resultSave2Excel = resultSave2Excel;
	}

	public ImportResultHandler<Ie, R> getImportResultHandler() {
		return importResultHandler;
	}

	public boolean isUpdateSupport() {
		return updateSupport;
	}

	public void setUpdateSupport(boolean updateSupport) {
		this.updateSupport = updateSupport;
	}

	public Class<R> getTargetClazz() {
		return targetClazz;
	}

}
